/*
This file is part of IRCaBot.
IRCaBot is IRC logger with features.
Source code: https://notabug.org/acetone/ircabot.
Copyright (C) acetone, 2023.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "httprequesttovoicegate.h"
#include "htmldocumentforvoicegate.h"
#include "voicegate.h"
#include "httpserver.h" // for constants
#include "dataparser.h"
#include "currenttime.h"
#include "zerostoragecaptcha.h"
#include "ircnetworkclient.h"
#include "config.h"

constexpr const uint8_t TIMEOUT_TOKEN_TIMEOUT = 30;

HttpRequestToVoiceGate::HttpRequestToVoiceGate(const QString &url) : m_mainToken( parser::get(url, "token", parser::GetType::Url) )
{
    if (m_mainToken.token().isEmpty())
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Access token is missing", "Bad request");
        return;
    }

    if (not m_mainToken.valid())
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Invalid access token", "Bad request");
        return;
    }

    for (const auto& network: Config::networks())
    {
        if (network->networkName == m_mainToken.payload().at(0))
        {
            if (not network->voiceGate)
            {
                m_document.setCode(http::CODE_403);
                m_document.setBodyStandardError("Voice gate has been disabled by the administrator", "Access denied", true);
                return;
            }
            break;
        }
    }

    HTTPRequestRouter::setUrl(url);
    m_url.remove(HTTP_URI_VOICEGATE_PREFIX);
    m_url.remove(0,1);

    if (m_url.startsWith(HTTP_URI_VOICEGATE_ACTION_INVITE))
    {
        onInvite();
    }
    else if(m_url.startsWith(HTTP_URI_VOICEGATE_ACTION_CHECK))
    {
        onCheck();
    }
    else if (m_url.startsWith(HTTP_URI_VOICEGATE_ACTION_BAD_CAPTCHA))
    {
        onBadCaptcha();
    }
    else if (m_url.startsWith(HTTP_URI_VOICEGATE_ACTION_TIMEOUT))
    {
        onTimeout();
    }
    else
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Voice gate: Unknown action", "Bad request");
        return;
    }
}

void HttpRequestToVoiceGate::onCheck() // captcha token (main) + invite
{
    const QString answer = parser::get(m_url, "answer", parser::GetType::Url);

    auto target = VoiceGate::verify(m_mainToken.token(), answer);
    if (target == nullptr)
    {
        const QString invite = parser::get(m_url, "invite", parser::GetType::Url);
        Token inviteToken(invite);
        if (not inviteToken.valid())
        {
            m_document.setCode(http::CODE_400);
            m_document.setBodyStandardError("Incorrect invite token", "Bad request");
            return;
        }
        m_mainToken = inviteToken;
        onInvite();
        return;
    }

    for (auto iter = Config::networksAccess().begin(); iter != Config::networksAccess().end(); iter++)
    {
        if ((*iter)->networkName == target->network)
        {
            if ((*iter)->instance == nullptr)
            {
                qCritical() << "HttpRequestToVoiceGate::onCheck find nullptr instanse for" << target->network << "network";
                m_document.setBodyStandardError("Internal error: unexpected nullptr pointer", "Internal error", true);
                return;
            }
            (*iter)->instance->addNicknameToVoicedGroup(target->nickname);
            successPage();
            return;
        }
    }

    m_document.setCode(http::CODE_500);
    m_document.setBodyStandardError("Captcha passed, but IRC network is not found in connections", "Internal error", true);
}

void HttpRequestToVoiceGate::onInvite() // only invite token
{
    bool timedout = false;
    auto captcha = VoiceGate::getCaptcha(m_mainToken.token(), &timedout);
    if (captcha == nullptr)
    {
        if (timedout)
        {
            timeoutPage();
        }
        else
        {
            m_document.setCode(http::CODE_406);
            m_document.setBodyStandardError("The data successfully passed the baseline check, "
                                            "but further processing was rejected because it was "
                                            "not correct in context.", "Not Acceptable", true);
        }
        return;
    }

    HTMLDocumentForVoiceGate html(":/private/html/voicegate/captcha.html");
    html.setValue("IMG_BASE64", captcha->imageB64);
    html.setValue("CAPTCHA_TOKEN", captcha->token);
    html.setValue("BAD_CAPTCHA_TOKEN", captcha->badToken);
    html.setValue("INVITE_TOKEN", m_mainToken.token());
    html.setValue("TIMEOUT_TOKEN", timeoutToken());

    m_document.setBody(html.data());
}

void HttpRequestToVoiceGate::onBadCaptcha() // bad captcha token + invite
{
    VoiceGate::badCaptchaReport(m_mainToken.token());

    const QString invite = parser::get(m_url, "invite", parser::GetType::Url);

    Token inviteToken(invite);
    if (not inviteToken.valid())
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Incorrect invite token", "Bad request");
        return;
    }
    m_mainToken = inviteToken;
    onInvite();
}

void HttpRequestToVoiceGate::onTimeout() // only timeout token
{
    if (m_mainToken.payload().size() != 1)
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Incorrect token", "Bad request");
        return;
    }

    qint64 timeoutTimestamp = m_mainToken.payload().at(0).toLongLong();
    if (timeoutTimestamp == 0)
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Incorrect token (timestamp corrupted)", "Bad request");
        return;
    }

    if (timeoutTimestamp > currentTimestampSecs())
    {
        m_document.setCode(http::CODE_400);
        m_document.setBodyStandardError("Please do not manually use service links", "Bad request");
        return;
    }

    if (currentTimestampSecs()-timeoutTimestamp <= TIMEOUT_TOKEN_TIMEOUT)
    {
        timeoutPage();
    }
    else
    {
        m_document.setCode(http::CODE_403);
        m_document.setBodyStandardError("Your link is out of date", "Access Denied");
    }
}

QString HttpRequestToVoiceGate::timeoutToken()
{
    Token token(QStringList() << QString::number(currentTimestampSecs()+ZSC_CAPTCHA_LONGEST_TIMEOUT-1));
    return token.token();
}

void HttpRequestToVoiceGate::timeoutPage()
{
    HTMLDocumentForVoiceGate html(":/private/html/voicegate/status.html");
    html.setValue("STATUS", "Timeout");
    QFile pic (":/private/html/voicegate/timeout.png");
    if (not pic.open(QIODevice::ReadOnly))
    {
        qCritical() << "HttpRequestToVoiceGate::timeoutPage() embedded picture not opened";
    }
    html.setValue("IMG_BASE64", pic.readAll().toBase64());
    pic.close();
    m_document.setBody(html.data());
}

void HttpRequestToVoiceGate::successPage()
{
    HTMLDocumentForVoiceGate html(":/private/html/voicegate/status.html");
    html.setValue("STATUS", "Success");
    QFile pic (":/private/html/voicegate/success.png");
    if (not pic.open(QIODevice::ReadOnly))
    {
        qCritical() << "HttpRequestToVoiceGate::successPage() embedded picture not opened";
    }
    html.setValue("IMG_BASE64", pic.readAll().toBase64());
    pic.close();
    m_document.setBody(html.data());
}
